#include "AlgoEvaluator.h"
#include "AlgoEvaluatorDB.h"
#include "AlgoEvaluatorModuleFactory.h"
#include "AlgoEvaluatorModuleBase.h"

class algo::AlgoEvaluatorPrivate
{
public:
	algo::AlgoEvaluator* q = nullptr;
	algobase::EvaluateL1IndexMap mEvaluateL1IndexMap;
	algobase::TaskConfig mTaskConfig;
	std::string m_lastErrorMessage;
	std::shared_ptr<AlgoEvaluatorDB> mDbCache;
};

algo::AlgoEvaluator::AlgoEvaluator()
	: d(new algo::AlgoEvaluatorPrivate)
{
	d->q = this;
}

algo::AlgoEvaluator::~AlgoEvaluator()
{
	delete d;
}

void algo::AlgoEvaluator::setTaskConfig(const algobase::TaskConfig& config)
{
	d->mTaskConfig = config;
	// d->mDbCache = nullptr;
}

algobase::TaskConfig& algo::AlgoEvaluator::getTaskConfig() const
{
	return d->mTaskConfig;
}

void algo::AlgoEvaluator::setEvaluateL1IndexMap(const algobase::EvaluateL1IndexMap& index)
{
	d->mEvaluateL1IndexMap = index;
	// d->mDbCache = nullptr;
}

algobase::EvaluateL1IndexMap& algo::AlgoEvaluator::getEvaluateL1IndexMap() const
{
	return d->mEvaluateL1IndexMap;
}

void algo::AlgoEvaluator::setEvaluateDB(const std::shared_ptr<algo::AlgoEvaluatorDB>& db)
{
	d->mDbCache = db;
}

std::shared_ptr<algo::AlgoEvaluatorDB> algo::AlgoEvaluator::getEvaluateDB() const
{
	return d->mDbCache;
}

static std::map<algobase::EvaluateL2IndexType, double> ParserIndexWeight(const algobase::EvaluateL1IndexMap& index)
{
	std::map<algobase::EvaluateL2IndexType, double> weightMap;
	for (const auto& l1Pair : index) {
		const auto& l2Array = l1Pair.second.second;
		for (const auto& l2Pair : l2Array) {
			if (l2Pair.second <= 0.0) {
				continue;
			} if (!weightMap.count(l2Pair.first)) {
				weightMap[l2Pair.first] = 0.0;
			}	weightMap[l2Pair.first] += l2Pair.second * l1Pair.second.first;
		}
	}

	double totalWeight = 0.0;
	for (const auto& weightPair : weightMap) {
		totalWeight += weightPair.second;
	} if (totalWeight > 0.0) {
		for (auto& weightPair : weightMap) {
			weightPair.second /= totalWeight;
		}
	}

	return weightMap;
}

static std::map<algobase::EvaluateL2IndexType, algobase::EvaluateL1IndexType> LineL2L1(const algobase::EvaluateL1IndexMap& index)
{
	std::map<algobase::EvaluateL2IndexType, algobase::EvaluateL1IndexType> l2l1Map;
	for (const auto& l1Pair : index) {
		const auto& l2Array = l1Pair.second.second;
		for (const auto& l2Pair : l2Array) {
			if (l2Pair.second <= 0.0) {
				continue;
			} if (!l2l1Map.count(l2Pair.first)) {
				l2l1Map[l2Pair.first] = l1Pair.first;
			}
		}
	}
	return l2l1Map;
}

int algo::AlgoEvaluator::exec(
	const algobase::Path& path,
	double& output_result,
	algobase::EvaluatorCoords& output_resultCoords)
{
	d->m_lastErrorMessage.clear();

	auto& config = d->mTaskConfig;
	auto& index = d->mEvaluateL1IndexMap;

	if (path.size() < 2) {
		d->m_lastErrorMessage = "Error: 'Path' Param Size <= 1!";
		return -1;
	} if (index.empty()) {
		d->m_lastErrorMessage = "Error: 'EvaluateL1IndexMap' Param Is Empty!";
		return -1;
	}

	output_result = 0.0;
	output_resultCoords.resize(path.size());
	for (int i = 0; i < output_resultCoords.size(); ++i) {
		output_resultCoords[i].Assign(path[i]);
	}

	auto L2L1Map = LineL2L1(index);
	auto L2IndexWeight = ParserIndexWeight(index);

	std::vector<algobase::EvaluateL2IndexType> L2Types;
	for (const auto& pair : L2IndexWeight) {
		L2Types.push_back(pair.first);
	}

	if (!d->mDbCache) {
		d->mDbCache = std::make_shared<algo::AlgoEvaluatorDB>(config, L2Types);
	}

	auto db = d->mDbCache;
	for (const auto& loop_type_weight : L2IndexWeight)
	{
		auto loop_module = algo::AlgoEvaluatorModuleFactory::Single()->create(loop_type_weight.first);
		if (!loop_module) {
			continue;
		}

		loop_module->setWeight(loop_type_weight.second);
		loop_module->setDB(db);
		loop_module->setTaskConfig(config);

		algobase::EvaluateModuleCoords loop_emCoords;
		double loop_module_result;

		int loop_exec_result = loop_module->exec(path, loop_module_result, loop_emCoords);
		if (path.size() != loop_emCoords.size()) {
			d->m_lastErrorMessage = "path's size != evaluateModuleCoords's size";
			return -1;
		}

		if (loop_exec_result == 0) {
			output_result += loop_module_result;
		}

		auto L1IndexType = L2L1Map[loop_type_weight.first];
		for (int i = 0; i < loop_emCoords.size(); ++i) {
			output_resultCoords[i].mResult += loop_emCoords[i].mResult;
			output_resultCoords[i].mL1EvaIndexArray[L1IndexType] += loop_emCoords[i].mResult;
			output_resultCoords[i].mL2EvaIndexArray[loop_type_weight.first] += loop_emCoords[i].mResult;
		}
	}

	auto hard_restriction_module = algo::AlgoEvaluatorModuleFactory::Single()->create(algobase::EL2IT_12HardRestriction);
	if (hard_restriction_module)
	{
		hard_restriction_module->setDB(db);
		hard_restriction_module->setTaskConfig(config);

		static algobase::EvaluateModuleCoords dummy_coords;
		double hard_restriction_result;

		int hard_module_exec = hard_restriction_module->exec(path, hard_restriction_result, dummy_coords);
		if (hard_module_exec != 0 ||
			path.size() != dummy_coords.size() ||
			hard_restriction_result <= 0.0) {
			output_result = 0.0;
		}
	}

	return 0;
}

std::string algo::AlgoEvaluator::getLastErrorMessage() const
{
	return d->m_lastErrorMessage;
}
